﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using BehaviorModel.Block.ServiceChannel;
using BehaviorModel.Statistic;
using ComputationalCore.Helper;
using Model;
using Model.Block;
using log4net;
using BehaviorModel.XmlSerializer;

namespace BehaviorModel.Block.Splitter
{
    public class Splitter : ISplitter
    {
        #region Fields

        //private static readonly ILog logger = LogManager.GetLogger(typeof (Splitter));

        private List<IReceiverBlock> _receiverBlocks;

        #endregion

        #region Properties

        #region IBlock

        public Guid Id { get; private set; }

        public BlockType Type { get { return BlockType.Splitter; } }

        public IBlockStatistic Statistic { get; private set; }

        #endregion

        #region ISplitter

        public SplitDiscipline Discipline { get; set; }

        public SplitterStorage Storage { get; private set; }

        #endregion

        #endregion

        #region Constructor

        public Splitter()
        {
            Id = Guid.NewGuid();
            Storage = new SplitterStorage();
            Statistic = new SplitterStatistic(this);
            _receiverBlocks = new List<IReceiverBlock>();
        }

        #endregion

        #region Methods

        #region IBlock

        public void Initialize()
        {
            Storage = new SplitterStorage();
            Statistic = new SplitterStatistic(this);
        }

        #endregion

        #region ISenderBlock

        public void ConnectReceiverBlock(IReceiverBlock block)
        {
            _receiverBlocks.Add(block);

            //logger.Info("Splitter is Adding Recieving Block" + block.GetType().ToString() + " with id: " + block.Id);
        }

        public void DisconnectReceiverBlock(IReceiverBlock receiverBlock)
        {
            _receiverBlocks.Remove(receiverBlock);
        }

        public void Send(Request request)
        {
            IReceiverBlock receiverBlock = GetRequiredBlockByUsingDiscipline();
            receiverBlock.Receive(request);
            IncCountOfPassed();
        }

        private IReceiverBlock GetRequiredBlockByUsingDiscipline()
        {
            switch (Discipline)
            {
                case SplitDiscipline.MinQueue:
                    return GetMinQueueBlock();
                case SplitDiscipline.EqualProbability:
                    return GetEqualProbabilityBlock();
                default:
                    throw new ArgumentNullException("Splitter discipline had not setted.");
            }
        }

        private IReceiverBlock GetMinQueueBlock()
        {
            var scBlocks = new List<IServiceChannel>();
            scBlocks.AddRange(_receiverBlocks.OfType<IServiceChannel>());
            scBlocks.Sort((a, b) => a.QueueLength.CompareTo(b.QueueLength));
            var block = scBlocks.Where(IsUnlimitedOrNotFullQueue()).Where(IsNotBusySCWithLoss()).FirstOrDefault();
            return block ?? scBlocks.FirstOrDefault();
        }

        private static Func<IServiceChannel, bool> IsNotBusySCWithLoss()
        {
            return block => !(block.ChannelType == ServiceChannelType.Loss && block.IsBusy);
        }

        private static Func<IServiceChannel, bool> IsUnlimitedOrNotFullQueue()
        {
            return block => block.MaxQueueLength == 0 || block.QueueLength < block.MaxQueueLength;
        }

        private IReceiverBlock GetEqualProbabilityBlock()
        {
            var rnd = RandomHelper.NextDouble();
            var count = _receiverBlocks.Count;

            return _receiverBlocks[(int)Math.Truncate(count * rnd)];
        }

        private void IncCountOfPassed()
        {
            Storage.CountOfPassed++;
        }

        #endregion

        #region IReceiverBlock

        public void Receive(Request request)
        {
            Send(request);
        }

        #endregion

        #endregion
    }
}